#pragma once
#include <string>
#include <cstring>
#include <sys/socket.h> // 核心Socket API（socket(), bind(), connect()等）
#include <netinet/in.h> // IPv4/IPv6 地址结构（struct sockaddr_in）
#include <arpa/inet.h>  // IP地址转换函数（inet_pton(), inet_ntop()）
#include <unistd.h>     // 文件描述符操作（close(), read(), write()）
#include <sys/wait.h>   //多进程需要
#include <signal.h>
#include <pthread.h>
#include "ThreadPool.hpp"

#include "log.hpp"
using namespace ThreadPoolModule;
using namespace LogModule;

static const uint16_t defaultport = 8080;

#define Die(code)   \
    do              \
    {               \
        exit(code); \
    } while (0)

#define CONV(v) (struct sockaddr *)(v)
class TcpServer
{
    using task_t = std::function<void()>;

private:
    struct ThreadData
    {
        int sockfd;
        TcpServer *self; // 其实就是this指针
    };

public:
    TcpServer(uint16_t port = defaultport)
        : _port(port),
          is_running(false)
    {
    }
    ~TcpServer()
    {
    }
    void InitServer()
    {
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "_socket create error";
            Die(1);
        }
        LOG(LogLevel::INFO) << "_socket create success, sockfd if :" << _listensockfd;

        // 接下来就是老规矩：bind
        // 在此之前，我们需要先创建一个struct sockaddr_in结构体并给他们赋予初始值
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(defaultport); // 进行本地到网络的转化
        local.sin_addr.s_addr = INADDR_ANY;  // 使用这个宏绑定套接字到本机的所有可用网络接口（即监听所有 IP 地址）

        int n = ::bind(_listensockfd, CONV(&local), sizeof(local)); // 绑定
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            Die(2);
        }
        LOG(LogLevel::INFO) << "bind success";

        // TCP是一种面向连接的协议，所以我们需要建立连接
        // 设置为监听状态
        n = ::listen(_listensockfd, 5);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            Die(3);
        }
        LOG(LogLevel::INFO) << "listen success, sockfd is : " << _listensockfd;

        //::signal(SIGCHLD, SIG_IGN); // 忽略子进程发给父进程的信号，让子进程结束时直接被内核回收
    }

    void Start()
    {
        is_running = true;
        while (is_running)
        {
            // 我们不能直接获取信息，但是要先建立连接：
            // 1、建立连接：accept
            struct sockaddr_in(peer);
            socklen_t peerlen = sizeof(peer);
            LOG(LogLevel::DEBUG) << "accept ing ...";
            // accept的返回值也是一个文件描述符，通过这个文件描述符我们可以与客户端进行通话
            // 因此这个文件描述符也具有全双工特性。7
            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);

            if (sockfd < 0)
            {
                LOG(LogLevel::WARN) << "accept error: " << strerror(errno);
                continue;
            }
            // 获取连接成功了
            LOG(LogLevel::INFO) << "accept success, sockfd is : " << sockfd;

            // // v1多进程版本
            // pid_t id = fork();

            // if (id == 0)
            // {
            //     // 此时子进程会进来，父进程会在外部
            //     // 子进程灰继承父进程的文件描述符表，所以需要关闭它，因为子进程不需要这个，防止因为引用计数的增加而导致出错
            //     ::close(_listensockfd);

            //     if (fork())
            //     {
            //         exit(0); // 子进程退出
            //         // 孙子进程 -> 孤儿进程 -> 1
            //         // 此时孙子进程会被我们的system，或者init这种pid为1的进程接管，由他们负责等待
            //     }

            //     HandlerRequest(sockfd);
            //     exit(0); // 子进程无论如何都是要退出的，不能执行后面的代码
            // }

            // ::close(sockfd);//走到这里的是父进程，关闭fd
            // // int rid = ::waitpid(id, nullptr, 0); // 父进程要进行等待

            // v2多线程版本

            // pthread_t tid;

            // ThreadData*data=new ThreadData();
            // data->sockfd=sockfd;//进行初始化
            // data->self=this;
            // pthread_create(&tid,nullptr,ThreadEntry,(void*)data);//我们这里通过再次回调的方式
            // //static解决的是类型不匹配的问题，ThreadData解决的是传参不能穿多个的问题

            // v3，线程池版本

            task_t t = std::bind(&TcpServer::HandlerRequest, this, sockfd); //
            // 把我们的类成员函数的类型绑定成一个void(void)
            ThreadPool<task_t>::getInstance()->Equeue([this, sockfd]()
                                                      { this->HandlerRequest(sockfd); });
        }
    }

    static void *ThreadEntry(void *args) // 回调调用我们的类成员函数
    {
        // 同样的原因，为了不让主线程去join等待，我们选择让副线程进行分离，销毁时自动回收资源
        pthread_detach(pthread_self()); // pthread_self()用于获取当前线程的标识符。
        ThreadData *data = (ThreadData *)args;
        int _sockfd = data->sockfd;
        data->self->HandlerRequest(_sockfd);
        return nullptr;
    }

    void HandlerRequest(int sockfd)
    {
        LOG(LogLevel::INFO) << "HandlerRequest, sockfd is : " << sockfd;
        char buffer[4096];
        while (true)
        {
            int n = ::read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0; // 手动置入一个结束标记
                std::string echo_str = "server echo$";
                echo_str += buffer;

                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0)
            {
                // n=0的情况我们说过，就是指的是服务端那里的write挂掉了，也就是说服务端退出了，那么我们此时约定，服务端也不再继续循环去读客户端的消息
                LOG(LogLevel::INFO) << "client quit: " << sockfd;
                break;
            }
            else
            {
                break; // 表示读取失败
            }
        }

        // 循环结束，不再使用fd
        ::close(sockfd);
    }

private:
    int _listensockfd; // 根据之前所学的UDP，我们可以知道，创建套接字其实就相当于打开了一个文件描述符。
    // 这个文件描述符时全双工的特性，也就是说，既可以读也可以写，所以我们这里就事先弄一个成员变量用来管理这个文件描述符
    bool is_running; // 服务端的运行状态
    uint16_t _port;
};